﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using JigLibX.Physics;

namespace DARE
{
    [AttrTypeDependentSerializable(typeof(DARE.Save.DataStructures.CSceneData))]
    public class CScene : ARenderable
    {

    #region fields

        private readonly Dictionary<string, CRender3D> m_render3ds = new Dictionary<string, CRender3D>();
        private readonly Dictionary<string, CRender2D> m_render2ds = new Dictionary<string, CRender2D>();
        private readonly Dictionary<string, CHUDSystem> HUDsystems = new Dictionary<string, CHUDSystem>();

        private CRender3D m_activeRender3d = null;
        private CRender2D m_activeRender2d = null;
        private CHUDSystem m_activeHudSystem = null;
        private CResourcesList m_resourceList = null;

        internal static CRender3D m_physicsRender =
            new CRender3D("physics render", new COctree(1000));

    #endregion

    #region properties

        /// <summary>
        /// Get or set the name of the scene, it is automaticaly changed in the scene manager
        /// </summary>
        public override string Name
        {
            get { return m_name; }
            set { CDare.Instance.SceneMgr.ChangeSceneName(m_name, value); m_name = value; }
        }

        public Dictionary<string, CRender3D> Render3D
        {
            get { return m_render3ds; }
        }

        public Dictionary<string, CRender2D> Render2D
        {
            get { return m_render2ds; }
        }

        public Dictionary<string, CHUDSystem> HUDSystem
        {
            get { return HUDsystems; }
        }

        public CResourcesList Resources
        {
            get { return this.m_resourceList; }
        }

        public CRender3D ActiveRender3D
        {
            get { return m_activeRender3d; }
            set { SetActiveRender(value); }
        }

        public CRender2D ActiveRender2D
        {
            get { return m_activeRender2d; }
            set { SetActiveRender(value); }
        }

        public CHUDSystem ActiveHudSystem
        {
            get { return m_activeHudSystem; }
            set { SetActiveRender(value); }
        }

    #endregion

    #region ctor

        /// <summary>
        /// Create a new scene and register it in the scene manager
        /// </summary>
        /// <param name="sceneName">Name of the scene to create</param>
        public CScene(string sceneName)
        {
            m_name = sceneName;
            m_resourceList = new CResourcesList();
            
            //m_gameObjectMgr.Initialize();
            CDare.Instance.SceneMgr.RegisterScene(this);

        }

        /// <summary>
        /// Default constructor
        /// </summary>
        public CScene()
        {
            m_resourceList = new CResourcesList();
        }

        #endregion

    #region implement

        public override void Update(GameTime gameTime)
        {
            if (m_activeHudSystem != null)
                m_activeHudSystem.Update(gameTime);
            if (m_activeRender2d != null)
                m_activeRender2d.Update(gameTime);
            if (m_activeRender3d != null)
                m_activeRender3d.Update(gameTime);

            if (m_activeRender3d != null)
            {
                foreach (CCamera cam in m_activeRender3d.CameraMgr.GetRenderingCameraList())
                {
                    m_activeRender3d.CameraMgr.SetActiveCamera(cam);

                    //DARE.CDare.Instance.GraphicsDevice.RasterizerState = RasterizerState.CullNone;
                    //DARE.CDare.Instance.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
                    //DARE.CDare.Instance.GraphicsDevice.BlendState = BlendState.AlphaBlend;

                    DARE.CDare.Instance.GraphicsDevice.SetRenderTarget(cam.ViewPort.RenderTarget);

                    DARE.CDare.Instance.GraphicsDevice.Clear(CDare.Instance.Config.Video.ClearColor);
                    m_activeRender3d.Draw(gameTime);
                }
                DARE.CDare.Instance.GraphicsDevice.SetRenderTarget(null);
            }
        }

        public override void Draw(GameTime gameTime)
        {
           
            if (m_activeRender3d != null)
            {
                foreach (CCamera cam in m_activeRender3d.CameraMgr.GetRenderingCameraList())
                {
                    cam.ViewPort.Draw();
                }
            }
            

            if (m_activeRender2d != null)
                m_activeRender2d.Draw(gameTime);
            if (m_activeHudSystem != null)
                m_activeHudSystem.Draw(gameTime);
        }

    #endregion

    #region methods

        /// <summary>
        /// Use to display the scene
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            string strScene;

            strScene = "Scene \"" + m_name + "\" { Render3D { ";
            foreach (CRender3D render in m_render3ds.Values)
                strScene += "\"" + render.Name + "\" ";
            strScene += "} Render2D { ";
            foreach (CRender2D render in m_render2ds.Values)
                strScene += "\"" + render.Name + "\" ";
            strScene += "} HUDSystem { ";
            foreach (CHUDSystem render in HUDsystems.Values)
                strScene += "\"" + render.Name + "\" ";
            strScene += "}";
            return strScene;
        }

        /// <summary>
        /// Throw an error if a renderer with this name already exists
        /// </summary>
        /// <param name="rendererName">Name of the renderer to check</param>
        private void CheckRendererExist(CRender3D render)
        {
            if (m_render3ds.ContainsValue(render))
                throw new Exception("Render3D \"" + render.Name + "\" is already in scene \"" + m_name + "\" !");
        }

        private void CheckRendererExist(CRender2D render)
        {
            if (m_render2ds.ContainsValue(render))
                throw new Exception("Render2D \"" + render.Name + "\" is already in scene \"" + m_name + "\" !");
        }

        private void CheckRendererExist(CHUDSystem render)
        {
            if (HUDsystems.ContainsValue(render))
                throw new Exception("HUDSystem \"" + render.Name + "\" is already in scene \"" + m_name + "\" !");
        }

        private void CheckNameExist(string name)
        {
            if (m_render3ds.ContainsKey(name) || 
                m_render2ds.ContainsKey(name) || 
                HUDsystems.ContainsKey(name))
                throw new Exception("A render with the name \"" + name + "\" already exists in scene \"" + m_name + "\" !");
        }

        /// <summary>
        /// Register a renderer
        /// </summary>
        /// <param name="renderer">Renderer to register</param>
        /// <returns>The registered renderer</returns>
        public CRender3D RegisterRenderer(CRender3D render)
        {
            CheckNameExist(render.Name);
            m_render3ds.Add(render.Name, render);
            if (m_activeRender3d == null)
                m_activeRender3d = render;
            render.EnalbePhysics = true;
            render.Scene = this;
            return render;
        }

        public CRender2D RegisterRenderer(CRender2D render)
        {
            CheckNameExist(render.Name);
            m_render2ds.Add(render.Name, render);
            if (m_activeRender2d == null)
                m_activeRender2d = render;
            render.Scene = this;
            return render;
        }

        public CHUDSystem RegisterRenderer(CHUDSystem render)
        {
            CheckNameExist(render.Name);
            HUDsystems.Add(render.Name, render);
            if (m_activeHudSystem == null)
                m_activeHudSystem = render;
            render.Scene = this;
            return render;
        }

        //public CRender3D CreateRender3D(string name)
        //{
        //    CheckNameExist(name);
        //    CRender3D render = new CRender3D(name);
        //    m_render3ds.Add(render.Name, render);
        //    if (m_activeRender3d == null)
        //        m_activeRender3d = render;
        //    render.Scene = this;
        //    return render;
        //}

        public CRender3D CreateRender3D(string name, IPartitioner partitioner)
        {
            CheckNameExist(name);
            CRender3D render = new CRender3D(name, partitioner);
            m_render3ds.Add(render.Name, render);
            if (m_activeRender3d == null)
                m_activeRender3d = render;
            render.Scene = this;
            return render;
        }

        public CRender2D CreateRender2D(string name)
        {
            CheckNameExist(name);
            CRender2D render = new CRender2D(name);
            m_render2ds.Add(render.Name, render);
            if (m_activeRender2d == null)
                m_activeRender2d = render;
            render.Scene = this;
            return render;
        }

        public CHUDSystem CreateHUDSystem(string name)
        {
            CheckNameExist(name);
            CHUDSystem render = new CHUDSystem(name);
            HUDsystems.Add(render.Name, render);
            if (m_activeHudSystem == null)
                m_activeHudSystem = render;
            render.Scene = this;
            return render;
        }

        public void SetActiveRender(CRender3D render)
        {
            if (render == null)
                return;

            //m_activeRender3d.EnalbePhysics = false;
            m_activeRender3d.IsActive = false;
            m_activeRender3d = render;
            m_activeRender3d.IsActive = true;
            //m_activeRender3d.EnalbePhysics = true;
            // because this fucking physics is singleton style without private constructor !!!
            PhysicsSystem.CurrentPhysicsSystem = m_activeRender3d.PhysicsSystem;
        }

        public void SetActiveRender(CRender2D render)
        {
            if (render == null)
                return;
            m_activeRender2d = render;
        }

        public void SetActiveRender(CHUDSystem render)
        {
            if (render == null)
                return;
            m_activeHudSystem = render;
        }

        public void SetActiveRender(string name)
        {
            if (m_render3ds.ContainsKey(name))
            {
                //m_activeRender3d.EnalbePhysics = false;
                m_activeRender3d.IsActive = false;
                m_activeRender3d = m_render3ds[name];
                m_activeRender3d.IsActive = true;

                //m_activeRender3d.EnalbePhysics = true;
                // because this fucking physics is singleton style without private constructor !!!
                PhysicsSystem.CurrentPhysicsSystem = m_activeRender3d.PhysicsSystem;
            }
            if (m_render2ds.ContainsKey(name))
                m_activeRender2d = m_render2ds[name];
            if (HUDsystems.ContainsKey(name))
                m_activeHudSystem = HUDsystems[name];
        }

        public void SetInactiveRender(string name)
        {
            if (m_activeRender3d != null && m_activeRender3d.Name == name)
            {
                m_activeRender3d.EnalbePhysics = false;
                m_activeRender3d = null;
            }
            if (m_activeRender2d != null && m_activeRender2d.Name == name)
                m_activeRender2d = null;
            if (m_activeHudSystem != null && m_activeHudSystem.Name == name)
                m_activeHudSystem = null;
        }

        public void SetInactiveRender(CRender3D render)
        {
            SetInactiveRender(render.Name);
        }

        public void SetInactiveRender(CRender2D render)
        {
            SetInactiveRender(render.Name);
        }

        public void SetInactiveRender(CHUDSystem render)
        {
            SetInactiveRender(render.Name);
        }

        public void UnregisterRenderer(string name)
        {
            if (m_render3ds.ContainsKey(name))
            {
                if (m_render3ds[name].Equals(m_activeRender3d))
                    m_activeRender3d = null;
                m_render3ds[name].EnalbePhysics = false;
                m_render3ds[name].Scene = null;
                m_render3ds[name].GameObjectMgr.Clear();
                m_render3ds[name].RemoveNodes();
                m_render3ds.Remove(name);
            }
            if (m_render2ds.ContainsKey(name))
            {
                if (m_render2ds[name].Equals(m_activeRender2d))
                    m_activeRender2d = null;
                m_render2ds[name].Scene = null;
                m_render2ds.Remove(name);
            }
            if (HUDsystems.ContainsKey(name))
            {
                if (HUDsystems[name].Equals(m_activeHudSystem))
                    m_activeHudSystem = null;
                HUDsystems[name].Scene = null;
                HUDsystems.Remove(name);
            }
        }

        /// <summary>
        /// Unregister a renderer
        /// </summary>
        /// <param name="renderer">Renderer to unregister</param>
        /// <returns>The renderer, or null if not found</returns>
        public CRender3D UnregisterRenderer(CRender3D render)
        {
            UnregisterRenderer(render.Name);
            render.Scene = null;
            return render;
        }

        public CRender2D UnregisterRenderer(CRender2D render)
        {
            UnregisterRenderer(render.Name);
            render.Scene = null;
            return render;
        }

        public CHUDSystem UnregisterRenderer(CHUDSystem render)
        {
            UnregisterRenderer(render.Name);
            render.Scene = null;
            return render;
        }

        /// <summary>
        /// Unregister all renderers
        /// </summary>
        /// <returns></returns>
        public void UnregisterRenderers()
        {
            m_activeRender3d = null;
            m_activeRender2d = null;
            m_activeHudSystem = null;
            foreach (CRender3D render in m_render3ds.Values)
            {
                render.EnalbePhysics = false;
                render.Scene = null;
            }
            m_render3ds.Clear();
            foreach (CRender2D render in m_render2ds.Values)
                render.Scene = null;
            m_render2ds.Clear();
            foreach (CHUDSystem render in HUDsystems.Values)
                render.Scene = null;
            HUDsystems.Clear();
        }

    #endregion

    }
}
